package types

import (
	"errors"
	. "github.com/spf13/cast"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

const (
	//URL相关选项
	URL_SCHEME_HTTP  = 1 //只允许http
	URL_SCHEME_HTTPS = 2 //只允许https
	URL_SCHEME_AUTO  = 4 //只允许自动判断协议

	//IP相关选项
	IP_V4 = 1 //只允许IPv4
	IP_V6 = 2 //只允许IPv6
)

var (
	privateIpReg = regexp.MustCompile(`^((10|127|0)(\.(0|[1-9]\d?|1\d{2}|2[0-4]\d|25[0-5])){3}|172\.(1[6-9]|2\d|31)(\.(0|[1-9]\d?|1\d{2}|2[0-4]\d|25[0-5])){2}|192\.168(\.(0|[1-9]\d?|1\d{2}|2[0-4]\d|25[0-5])){2})$`)
	ipv4Reg      = regexp.MustCompile(`^(0|[1-9]\d?|1\d{2}|2[0-4]\d|25[0-5])(\.(0|[1-9]\d?|1\d{2}|2[0-4]\d|25[0-5])){3}`)
	ipv6Reg      = regexp.MustCompile(`^[1-9A-Fa-f][1-9A-Fa-f]{0,3}(:[0-9A-Fa-f]{1,4}){7}$`)
	emailReg     = regexp.MustCompile(`^[a-zA-Z\d]([\w\.\-]*[a-zA-Z\d])*@(([a-zA-Z\d][a-zA-Z\d\-]*)?[a-zA-Z\d]\.)+[a-zA-Z]{2,10}`)
)

type structValidator struct {
	Value      interface{}
	r          reflect.Value //反射获取到的句柄
	TagKey     string
	currentVal interface{} //当前解析字段值
}

func (s *structValidator) do() error {
	s.r = reflect.ValueOf(s.Value)

	//指针类型结构体判断
	if s.r.Kind() == reflect.Ptr && s.r.Elem().Kind() == reflect.Struct {
		s.r = s.r.Elem()
	} else if s.r.Kind() != reflect.Struct {
		return errors.New("type error! just support struct")
	}

	return s.fields()
}

//获取结构体中所有字段的值，并根据标签格式进行验证
func (s *structValidator) fields() error {
	var (
		params map[string][]interface{}
		err    error = nil
	)

	for i := 0; i < s.r.NumField(); i++ {
		info := s.r.Field(i)
		//小写开头的成员是不能被反射获取
		if !info.CanInterface() {
			continue
		}

		s.currentVal = info.Interface()
		params = s.parseTag(s.r.Type().Field(i).Tag.Get(s.TagKey))

		for key, item := range params {
			if _, err = s.execute(key, item...); nil != err {
				return err
			}
		}
	}

	return err
}

//解析结构体标签说明，以函数名=>参数列表形式返回
func (s *structValidator) parseTag(tag string) map[string][]interface{} {
	var (
		result map[string][]interface{}
		tags   []string = strings.Split(tag, ";")
		match  []string
		handle *regexp.Regexp = regexp.MustCompile(`^\s*([\w]+)\s*(\(.*\))?\s*$`)
		item   string
	)

	result = make(map[string][]interface{})

	for _, item = range tags {
		match = handle.FindStringSubmatch(item)

		switch len(match) {
		case 3:
			if status, _ := regexp.MatchString(`^\(\s*\)$`, match[2]); !status {
				result[match[1]] = s.paramsSplit(match[2])
				continue
			}
			fallthrough
		case 2:
			result[match[1]] = []interface{}{s.currentVal}
		}
	}

	if _, ok := result["Required"]; !ok && IsEmpty(s.currentVal) {
		result = nil
	}

	return result
}

//解析结构体标签说明里面的函数参数
func (s *structValidator) paramsSplit(param string) []interface{} {
	var (
		startChar byte          = ','
		index     int           = 0
		paramChar []byte        = []byte(param)
		chunk     []byte        = make([]byte, 100)
		result    []interface{} = make([]interface{}, 0, 6)
		paramLen  int           = len(paramChar) - 1
	)

	result = append(result, s.currentVal)

	for i := 1; i < paramLen; i++ {
		if '\'' != paramChar[i] && (',' != paramChar[i] || startChar == '\'') {
			chunk[index] = paramChar[i]
			index++

			if i < paramLen-1 {
				continue
			}
		}

		if index > 0 {
			result = append(result, string(chunk[:index]))
			index = 0
		}
		startChar = paramChar[i]
	}

	return result
}

//根据参数，执行对应检测函数
func (s *structValidator) execute(name string, args ...interface{}) (status bool, err error) {
	switch name {
	case "Required":
		status = Required(args[0])
	case "IsInteger":
		status = IsInteger(args[0])
	case "IsFloat":
		status = IsFloat(args[0])
	case "IsEmail":
		status = IsEmail(args[0])
	case "IsMobilePhone":
		status = IsMobilePhone(args[0])
	case "IsURL":
		status = IsURL(args[0]) //auto
	case "IsInRange":
		status = IsInRange(args...)
	case "IsIDCard":
		status = IsIDCard(ToString(args[0]))
	case "IsChineseChar":
		status = IsChineseChar(args...)
	case "IsIP":
		status = IsIP(args[0]) //auto
	case "IsInSizeRange":
		status = IsInSizeRange(args...)
	case "IsPrivateIp":
		status = IsPrivateIp(ToString(args[0]))
	default:
		status = false
		err = errors.New("not support function name:" + name)
	}

	if !status {
		err = errors.New(ToString(args[0]) + " valid failed through: " + name)
	}

	return
}

//进行结构体验证
func StructValidator(args ...interface{}) error {
	var (
		tagKey string = "forms"
		status bool   = false
	)

	if len(args) > 1 {
		if tagKey, status = args[1].(string); !status {
			tagKey = "forms"
		}
	}

	return (&structValidator{Value: args[0], TagKey: tagKey}).do()
}

/*********************** 值检测函数，统一返回bool ************************/

//检测是否为空(值为""或nil时判定为空)
func IsEmpty(value interface{}) bool {
	return "" == value || nil == value
}

//检测值不为空,和IsEmpty返回值相反
func Required(value interface{}) bool {
	return "" != value && nil != value
}

//检测是否为整数，排除以0开头，例如"023"不算整数,支持负数
func IsInteger(value interface{}) bool {
	result := false

	switch v := value.(type) {
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		result = true
	case string:
		result, _ = regexp.MatchString(`^\-?[1-9]\d*$`, v)
	}

	return result
}

//检测是否是浮点数
func IsFloat(value interface{}) bool {
	result := false

	switch v := value.(type) {
	case float32, float64:
		result = true
	case string:
		result, _ = regexp.MatchString(`^\-?([1-9]\d*|0)\.\d+$`, v)
	}

	return result
}

//检测是否是email
func IsEmail(value interface{}) bool {
	email, result := value.(string)

	if result {
		result = emailReg.MatchString(email)
	}

	return result
}

//检测是否是手机
func IsMobilePhone(value interface{}) bool {
	var (
		result  bool  = false
		segment int64 = 0
	)

	switch v := value.(type) {
	case int64:
		segment = v / 100000000 //这种情况只可能是手机号
	case uint64:
		segment = int64(v) / 100000000
	case int: //int在64位系统下默认为int64
		segment = int64(v) / 100000000
	case uint:
		segment = int64(v) / 100000000
	case string:
		handle := regexp.MustCompile(`^(1[34578]\d)\d{8}$`)
		match := handle.FindStringSubmatch(v)
		if len(match) == 2 {
			segment, _ = strconv.ParseInt(match[1], 10, 64)
		}
	}

	if (segment >= 130 && segment <= 139) ||
		(segment == 145 || segment == 147) ||
		(segment >= 150 && segment <= 159 && segment != 154) ||
		(segment >= 180 && segment <= 189) ||
		(segment >= 170 && segment <= 173) ||
		(segment >= 176 && segment <= 178) ||
		(segment == 166) ||
		(segment >= 198 && segment <= 199) {
		result = true
	} else {
		result = false
	}

	return result
}

//检测是否是URL格式数据
func IsURL(args ...interface{}) bool {
	var (
		argsLen int    = 0
		result  bool   = false
		reg     string = `\/\/(([a-zA-Z\d][a-zA-Z\d\-]*)?[a-zA-Z\d]\.)+[a-zA-Z]{2,10}\/?[\w\.\-\!#&=\?\/]*$`
		option  int    = 0
		url     string
	)

	argsLen = len(args)

	switch argsLen {
	case 2:
		option, result = args[1].(int)

		fallthrough
	case 1:
		url, result = args[0].(string)

		switch option {
		case URL_SCHEME_HTTP:
			reg = `^http:` + reg
		case URL_SCHEME_HTTPS:
			reg = `^https:` + reg
		case URL_SCHEME_AUTO:
			reg = `^` + reg
		case URL_SCHEME_HTTP | URL_SCHEME_HTTPS:
			reg = `^https?:` + reg
		case URL_SCHEME_HTTP | URL_SCHEME_AUTO:
			reg = `^(http:)?` + reg
		case URL_SCHEME_AUTO | URL_SCHEME_HTTPS:
			reg = `^(https:)?` + reg
		default:
			reg = `^(https?:)?` + reg
		}
	}

	if result {
		result, _ = regexp.MatchString(reg, url)
	}

	return result
}

//检测值是否在指定区间中(包含min和max)
func IsInRange(args ...interface{}) bool {
	var (
		result bool    = false
		number float64 = 0
	)

	switch len(args) {
	case 0, 1:
	case 2:
		//检测值格式是否正常
		result = IsInteger(args[0]) || IsFloat(args[0])
		result = result && (IsInteger(args[1]) || IsFloat(args[1]))
		result = result && (ToFloat64(args[0]) == ToFloat64(args[1]))
	default:
		number = ToFloat64(args[0])
		result = IsInteger(args[0]) || IsFloat(args[0])
		result = result && (IsInteger(args[1]) || IsFloat(args[1]))
		result = result && (IsInteger(args[2]) || IsFloat(args[2]))
		result = result && (number >= ToFloat64(args[1]) && number <= ToFloat64(args[2]))
	}

	return result
}

//检测值是否在指定列表中，只支持数值和字符串检测
func IsInArray(value interface{}, arr []interface{}) bool {
	var (
		result   bool   = false
		valueStr string = ToString(value)
	)

	for _, item := range arr {
		if valueStr == ToString(item) {
			result = true
		}
	}

	return result
}

//检测是否是安全的字符串
func IsSecureString(value string) bool {
	result, err := regexp.MatchString(`[\\'"]`, value)

	if nil != err {
		result = false
	}

	return result
}

//利用正则进行检测
func IsMatch(value interface{}, regex string) bool {
	var (
		result   bool   = false
		valueStr string = ToString(value)
	)

	result, _ = regexp.MatchString(regex, valueStr)

	return result
}

//检测是否是身份证(18位)，身份证可能有个X，所以必定是字符串
func IsIDCard(value string) bool {
	var (
		result     bool   = false
		sum        uint64 = 0
		start      uint64 = 1
		lastNumber []rune
	)

	if result, _ = regexp.MatchString(`^[1-8]{2}\d{15}[\dxX]$`, value); result {

		arrStr := []rune(value)
		lastNumber = []rune{'1', '0', 'x', '9', '8', '7', '6', '5', '4', '3', '2'}

		if 'X' == arrStr[17] {
			arrStr[17] = 'x'
		}

		for i, number := range arrStr {
			if i < 17 {
				start <<= 1
				sum += (uint64(number) - 48) * start
			}
		}

		result = lastNumber[sum%11] == arrStr[17]
	}

	return result
}

//检测是否是中文
func IsChineseChar(args ...interface{}) bool {
	var (
		result   bool = false
		regex, v string
	)

	switch len(args) {
	case 1:
		regex = `^[\p{Han}]+$`
	case 2:
		regex = `^[\p{Han}]{` + ToString(args[1]) + `}$`
	case 3:
		regex = `^[\p{Han}]{` + ToString(args[1]) + `,` + ToString(args[2]) + `}$`
	default:
		return false
	}

	if v, result = args[0].(string); result {
		result, _ = regexp.MatchString(regex, v)
	}

	return result
}

//检测是否是IP
func IsIP(args ...interface{}) bool {
	var (
		ip     string
		result = false
		option = 0
	)

	switch len(args) {
	case 2:
		option, result = args[1].(int)
		fallthrough
	case 1:
		if ip, result = args[0].(string); !result {
			option = -1
		}
	}

	switch option {
	default:
		if result = ipv4Reg.MatchString(ip); result {
			return result
		}
		//非ipv4，则进行ipv6判断
		fallthrough
	case IP_V6:
		result = ipv6Reg.MatchString(ip)

		if result && (strings.Count(ip, "::") > 1 || strings.Count(ip, ":::") > 0) {
			result = false
		}
	case IP_V4:
		result = ipv4Reg.MatchString(ip)
	case -1:
	}

	return result
}

//检查内容是否在指定长度
func IsInSizeRange(args ...interface{}) bool {
	var (
		regex  string = ``
		result bool   = false
		str    string
	)

	switch len(args) {
	case 0, 1:
		return false
	case 2:
		regex = `^.{` + ToString(args[1]) + `}$`
	default:
		regex = `^.{` + ToString(args[1]) + `,` + ToString(args[2]) + `}$`
	}

	if str, result = args[0].(string); result {
		result, _ = regexp.MatchString(regex, str)
	}

	return result
}

//检测是否是内网IP
func IsPrivateIp(ip string) bool {
	return privateIpReg.MatchString(ip)
}
